"""
# -*- coding: utf-8 -*-
# @Time    : 2023/5/23 11:10
# @Author  : 王摇摆
# @FileName: Model_Manual.py
# @Software: PyCharm
# @Blog    ：https://blog.csdn.net/weixin_44943389?type=blog
"""
import numpy as np
from sklearn.tree import DecisionTreeRegressor


class adaboostr():
    """
    AdaBoost 回归算法
    """

    def __init__(self, n_estimators=100):
        # AdaBoost弱学习器数量
        self.n_estimators = n_estimators
        print('人工随机森林AdaBoost回归器已初始化完毕！')

    def fit(self, X, y):
        """
        AdaBoost 回归算法拟合
        """
        # 初始化样本权重向量
        sample_weights = np.ones(X.shape[0]) / X.shape[0]
        # 估计器数组
        estimators = []
        # 估计器权重数组
        weights = []
        # 遍历估计器
        for i in range(self.n_estimators):
            # 初始化最大深度为3的决策树估计器
            estimator = DecisionTreeRegressor(max_depth=3)
            # 根据样本权重拟合训练集
            estimator.fit(X, y, sample_weight=sample_weights)
            # 预测结果
            y_predict = estimator.predict(X)
            # 计算误差向量（线性误差）
            errors = np.abs(y_predict - y)
            errors = errors / np.max(errors)
            # 计算误差率
            e = np.sum(np.multiply(errors, sample_weights))
            # 当误差率大于等于0.5时跳出循环
            if e >= 0.5:
                self.n_estimators = i
                break
            # 计算估计器权重
            weight = e / (1 - e)
            # 计算样本权重
            temp_weights = np.multiply(sample_weights, np.power(weight, 1 - errors))
            # 归一化样本权重
            sample_weights = temp_weights / np.sum(temp_weights)
            weights.append(weight)
            estimators.append(estimator)
        self.weights = np.array(weights)
        self.estimators = np.array(estimators)

    def predict(self, X):
        """
        AdaBoost 回归算法预测
        """
        # 论文中权重的定义
        weights = np.log(1 / self.weights)
        # 预测结果矩阵
        predictions = np.array([self.estimators[i].predict(X) for i in range(self.n_estimators)]).T
        # 根据预测结果排序后的下标
        sorted_idx = np.argsort(predictions, axis=1)
        # 根据排序结果依次累加估计器权重，得到新的累积权重矩阵，类似累积分布函数的定义
        weight_cdf = np.cumsum(weights[sorted_idx], axis=1, dtype=np.float64)
        # 累积权重矩阵中大于其中中位数的结果
        median_or_above = weight_cdf >= 0.5 * weight_cdf[:, -1][:, np.newaxis]
        # 中位数结果对应的下标
        median_idx = median_or_above.argmax(axis=1)
        # 对应的估计器
        median_estimators = sorted_idx[np.arange(X.shape[0]), median_idx]
        # 取对应的估计器的预测结果作为最后的结果
        return predictions[np.arange(X.shape[0]), median_estimators]
